#include "serial.h"
using namespace comm;

#ifdef WIN32
// CSerialPort
//
// Description:	Standard constructor.
//
CSerialPort::CSerialPort (unsigned int uiTxBufferSize, unsigned int uiRxBufferSize) {
	m_uiTxBufferSize = uiTxBufferSize;
	m_uiRxBufferSize = uiRxBufferSize;

	m_KillEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
	m_RxEvent = CreateEvent (NULL, FALSE, FALSE, NULL);

	m_ReadOverlapped.Offset = 0;
	m_ReadOverlapped.OffsetHigh = 0;
	m_ReadOverlapped.hEvent = 0; // (HANDLE)m_ReadEvent;

	m_WriteOverlapped.Offset = 0;
	m_WriteOverlapped.OffsetHigh = 0;
	m_WriteOverlapped.hEvent = 0; // (HANDLE)m_WriteEvent;

	m_RxOverlapped.Offset = 0;
	m_RxOverlapped.OffsetHigh = 0;
	m_RxOverlapped.hEvent = m_RxEvent;

	m_hComm = NULL;
}

// ~CSerialPort
//
// Description:	Standard destructor.
//
CSerialPort::~CSerialPort () {
	if  (GetOpenStatus ())
		Close ();
}

// CSerialPort::PortNumber2String
//
// Description: Open the serial port. In case of data reception on the
//              serial port either the windows message mechanism or a callback
//              function mechanism can be used for application notification.
//
// Return:      bool     -  true if successful, otherwise false
//
// Parameter:   iPortNumber             -   positive RS232 port index 1 = COM1 = /dev/ttyS0
//              pcDestination           -   destination string buffer, must be long enough  (whatever that means)
//
bool CSerialPort::PortNumber2String (int iPortNumber, char * pcDestination) {
	if  (iPortNumber < 1)
		return false;
	sprintf (pcDestination, "COM%d", iPortNumber);
	return true;
}

// CSerialPort::Open
//
// Description:	Open the serial port. In case of data reception on the
//				serial port either the windows message mechanism or a callback
//				function mechanism can be used for application notification.
//
// Return:		bool	-	true if successful, otherwise false
//
// Parameter:	EPort Port				-	Port number  (PORT_1_E, PORT_2_E, PORT_3_E, PORT_4_E).
//				int iBaud				-	Baud rate  (e.g. 4800, 19200, ...).
//				EDataBits DataBits		-	Data bits  (DATA_BITS_5_E, DATA_BITS_6_E, DATA_BITS_7_E, DATA_BITS_8_E = 8).
//				EStopBits StopBits		-	Stop bits  (STOP_BITS_1_E, STOP_BITS_1P5_E, STOP_BITS_2_E).
//				EParity Parity			-	Parity  (NO_PARITY_E, ODD_PARITY_E, EVEN_PARITY_E, MARK_PARITY_E, SPACE_PARITY_E).
//				EFlow Flow				-	Flow control  (FLOW_CONTROL_NONE_E).
bool CSerialPort::Open (const char * pcPort, int iBaud, EDataBits DataBits, EStopBits StopBits, EParity Parity, EFlow Flow) {
	try {
		// Check if already opened:
		if  (m_hComm != NULL)
			return false;

		strncpy (m_acPort, pcPort, sizeof (m_acPort));
		m_acPort[sizeof (m_acPort) - 1] = 0;  // make sure destination string is null terminated
		m_iBaud = iBaud;
		m_enDataBits = DataBits;
		m_enStopBits = StopBits;
		m_enParity = Parity;
		m_enFlow = Flow;

		// Create Com handle:
		m_hComm = CreateFile (	pcPort,
		                      GENERIC_READ | GENERIC_WRITE,
		                      0,
		                      0,
		                      OPEN_EXISTING,
		                      FILE_FLAG_OVERLAPPED,
		                      0);

		if  (m_hComm == INVALID_HANDLE_VALUE)
			return false;

		// Set Tx and Rx buffer size:
		if  (SetupComm (m_hComm, m_uiRxBufferSize, m_uiTxBufferSize) == FALSE)
			return false;

		// Fill DCB structure:
		m_Dcb.DCBlength = sizeof (DCB);				// sizeof (DCB)
		m_Dcb.fBinary = 1;							// binary mode, no EOF check
		m_Dcb.fParity = 1;							// enable parity checking
		m_Dcb.fErrorChar = 0;						// enable error replacement
		m_Dcb.fNull = 0;							// enable null stripping
		m_Dcb.fAbortOnError = 0;					// abort reads/writes on error
		m_Dcb.wReserved = 0;						// not currently used
		m_Dcb.ErrorChar = 0;						// error replacement character
		m_Dcb.EofChar = 0;							// end of input character
		m_Dcb.EvtChar = 0;							// received event character
		m_Dcb.XonLim = 0;							// transmit XON threshold
		m_Dcb.XoffLim = 0;							// transmit XOFF threshold
		m_Dcb.fTXContinueOnXoff = 1;				// XOFF continues Tx
		m_Dcb.fOutX = 0;							// XON/XOFF out flow control
		m_Dcb.fInX = 0;								// XON/XOFF in flow control
		m_Dcb.XonChar = 0;							// Tx and Rx XON character
		m_Dcb.XoffChar = 0;							// Tx and Rx XOFF character

		m_Dcb.BaudRate = m_iBaud;					// current baud rate
		m_Dcb.ByteSize = m_enDataBits;				// number of bits/byte, 4-8
		m_Dcb.Parity = m_enParity;					// 0-4=no,odd,even,mark,space
		m_Dcb.StopBits = m_enStopBits;				// 0,1,2 = 1, 1.5, 2

		switch  (m_enFlow) {
			case FLOW_CONTROL_CTS_E:
				m_Dcb.fRtsControl = RTS_CONTROL_ENABLE;     // RTS output for instream flow control
				m_Dcb.fOutxCtsFlow = TRUE;                  // CTS input for outstream flow control
				m_Dcb.fDtrControl = DTR_CONTROL_ENABLE;     // DTR output for instream flow control
				m_Dcb.fOutxDsrFlow = FALSE;                 // DSR input for outstream flow control
				m_Dcb.fDsrSensitivity = FALSE;              // DSR sensitivity
				break;

			case FLOW_CONTROL_NONE_E:
			default:
				m_Dcb.fRtsControl = RTS_CONTROL_ENABLE;     // RTS output for instream flow control
				m_Dcb.fOutxCtsFlow = FALSE;                 // CTS input for outstream flow control
				m_Dcb.fDtrControl = DTR_CONTROL_ENABLE;     // DTR output for instream flow control
				m_Dcb.fOutxDsrFlow = FALSE;                 // DSR input for outstream flow control
				m_Dcb.fDsrSensitivity = FALSE;              // DSR sensitivity
				break;
		}

		// Initialize port:
		if  (SetCommState (m_hComm, &m_Dcb) == FALSE) {
			DWORD dw = GetLastError ();

			Close ();
			return false;
		}

		// Set timeouts:
		m_CommTimeouts.ReadIntervalTimeout = MAXDWORD ;
		m_CommTimeouts.ReadTotalTimeoutMultiplier = MAXDWORD ;
		m_CommTimeouts.ReadTotalTimeoutConstant = 1;
		m_CommTimeouts.WriteTotalTimeoutMultiplier = 0;
		m_CommTimeouts.WriteTotalTimeoutConstant = 0;

		if  (SetCommTimeouts (m_hComm, &m_CommTimeouts) == FALSE) {
			Close ();
			return false;
		}

		if  (SetCommMask (m_hComm, EV_RXCHAR) == FALSE)
			return false;


		Reset ();

		return true;
	} catch  (...) {
		return false;
	}
}

// Close
//
// Description:	Closes the opened port. Can be called directly, but is also
//				called automatically when the CSerialPort object is destructed.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	void
//
bool CSerialPort::Close (void) {
	try {
		SetEvent (m_KillEvent);

		if  (m_hComm != NULL) {
			CloseHandle (m_hComm);
			m_hComm = NULL;
		}

		return true;
	} catch  (...) {
		return false;
	}
}

// Reset
//
// Description: Reset port. Rx and Tx buffers are flushed.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	void
//
bool CSerialPort::Reset (void) {
	try {
		if  (m_hComm == NULL)
			return false;
		if  (PurgeComm (m_hComm, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR) == FALSE)
			return false;

		return true;
	} catch  (...) {
		return false;
	}
}

// Read
//
// Description:
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	unsigned char * ucBuffer
//				unsigned int uiNumOfBytes
//				unsigned int &uiNumOfBytesRead
//
bool CSerialPort::Read (void * pvBuffer, unsigned int uiNumOfBytes, unsigned long * pulNumOfBytesRead) {
	try {
		* pulNumOfBytesRead = 0;

		m_ReadOverlapped.Offset = 0;
		m_ReadOverlapped.OffsetHigh = 0;

		DWORD dwErrors = 0;
		if  (ClearCommError (m_hComm, &dwErrors, &m_ComStat) == FALSE) {
			return false;
		}
		if  (m_ComStat.cbInQue < uiNumOfBytes)
			uiNumOfBytes = m_ComStat.cbInQue;

		if  (ReadFile (m_hComm, pvBuffer, uiNumOfBytes, pulNumOfBytesRead, &m_ReadOverlapped) == FALSE) {
			switch  (GetLastError ()) {
				case ERROR_IO_PENDING: {
						if  (GetOverlappedResult (m_hComm, &m_ReadOverlapped, pulNumOfBytesRead, FALSE) == FALSE) {
							if  (pulNumOfBytesRead != 0)
								return true;
						}

						return false;
					}

				default: {
						return false;
					}
			}
		}

		return true;
	} catch  (...) {
		return false;
	}
}

// Write
//
// Description:	Write one or more bytes to the serial port.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	unsigned char * pucBuffer			-	Contains the data to
//														be written.
//				unsigned int uiNumOfBytes			-	Number of bytes to
//														write.
//
bool CSerialPort::Write (void * pvBuffer, unsigned int uiNumOfBytes) {
	try {
		m_WriteOverlapped.Offset = 0;
		m_WriteOverlapped.OffsetHigh = 0;
		DWORD dwWritten;

		if  (WriteFile (m_hComm, pvBuffer, uiNumOfBytes, &dwWritten, &m_WriteOverlapped) == FALSE) {
			switch  (GetLastError ()) {
				case ERROR_IO_PENDING: {
						return true;
					}

				default: {
						/* 
											DWORD dwError = GetLastError ();
											LPVOID lpMsgBuf;
											FormatMessage ( 
												FORMAT_MESSAGE_ALLOCATE_BUFFER | 
												FORMAT_MESSAGE_FROM_SYSTEM | 
												FORMAT_MESSAGE_IGNORE_INSERTS,
												NULL,
												dwError,
												MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
												 (LPTSTR) &lpMsgBuf,
												0,
												NULL 
											);
											// Process any inserts in lpMsgBuf.
											// ...
											// Display the string.
											printf ("[%lu:%s]", dwError,  (LPCTSTR)lpMsgBuf);
											// Free the buffer.
											LocalFree ( lpMsgBuf );
						 */
						return false;
					}
			}
		}

		return true;
	} catch  (...) {
		return false;
	}
}


bool CSerialPort::ReadBlocking (void * pvBuffer, unsigned int uiNumOfBytes, unsigned long * pulNumOfBytesRead) {
	try {
		if  (!Read (pvBuffer, uiNumOfBytes, pulNumOfBytesRead))
			return false;
		if  (* pulNumOfBytesRead > 0)
			return true;

		HANDLE aEventHandles[2] = {	m_KillEvent,  (HANDLE)m_RxEvent };
		DWORD dwEventMask = 0;

		if  (WaitCommEvent (m_hComm, &dwEventMask, &m_RxOverlapped) == FALSE) {
			switch  (GetLastError ()) {
				case ERROR_IO_PENDING:
					break;

				default:
					return false;
			}
		}

		switch  (WaitForMultipleObjects (2, aEventHandles, FALSE, INFINITE)) {
			case 1:
				if  ( (dwEventMask & EV_RXCHAR) == EV_RXCHAR) {
					return Read (pvBuffer, uiNumOfBytes, pulNumOfBytesRead);
				}
				return true;

			default:
				return false;
		}
	} catch  (...) {
		return false;
	}
}
#else

// CSerialPort
//
// Description:	Standard constructor.
//
CSerialPort::CSerialPort (unsigned int uiTxBufferSize, unsigned int uiRxBufferSize) {
	m_uiTxBufferSize = uiTxBufferSize;
	m_uiRxBufferSize = uiRxBufferSize;

	m_hComm = -1;
}

// ~CSerialPort
//
// Description:	Standard destructor.
//
CSerialPort::~CSerialPort () {
	if  (GetOpenStatus ())
		Close ();
}

// CSerialPort::PortNumber2String
//
// Description: Open the serial port. In case of data reception on the
//              serial port either the windows message mechanism or a callback
//              function mechanism can be used for application notification.
//
// Return:      bool     -  true if successful, otherwise false
//
// Parameter:   iPortNumber             -   positive RS232 port index 1 = COM1 = /dev/ttyS0
//              pcDestination           -   destination string buffer, must be long enough  (whatever that means)
//
bool CSerialPort::PortNumber2String (int iPortNumber, char * pcDestination) {
	if  (iPortNumber < 1)
		return false;
	sprintf (pcDestination, "/dev/ttyS%d", iPortNumber - 1);
	return true;
}

// CSerialPort::Open
//
// Description:	Open the serial port. In case of data reception on the
//				serial port either the windows message mechanism or a callback
//				function mechanism can be used for application notification.
//
// Return:		bool	-	true if successful, otherwise false
//
// Parameter:	EPort Port				-	Port number  (PORT_1_E, PORT_2_E, PORT_3_E, PORT_4_E).
//				int iBaud				-	Baud rate  (e.g. 4800, 19200, ...).
//				EDataBits DataBits		-	Data bits  (DATA_BITS_5_E, DATA_BITS_6_E, DATA_BITS_7_E, DATA_BITS_8_E = 8).
//				EStopBits StopBits		-	Stop bits  (STOP_BITS_1_E, STOP_BITS_1P5_E, STOP_BITS_2_E).
//				EParity Parity			-	Parity  (NO_PARITY_E, ODD_PARITY_E, EVEN_PARITY_E, MARK_PARITY_E, SPACE_PARITY_E).
//				EFlow Flow				-	Flow control  (FLOW_CONTROL_NONE_E).
//
bool CSerialPort::Open (const char * pcPort, int iBaud, EDataBits DataBits, EStopBits StopBits, EParity Parity, EFlow Flow) {
	try {
		// Check if already opened:
		if  (m_hComm >= 0)
			return false;

		strncpy (m_acPort, pcPort, sizeof (m_acPort));
		m_acPort[sizeof (m_acPort) - 1] = 0;  // make sure destination string is null terminated
		m_iBaud = iBaud;
		m_enDataBits = DataBits;
		m_enStopBits = StopBits;
		m_enParity = Parity;
		m_enFlow = Flow;

		// Open serial device for read/write and no console capability:
		m_hComm = open (pcPort, O_RDWR | O_NOCTTY);
		if  (m_hComm < 0) {
			printf ("CSerialPort::Open () failed on %s  (check access permission, ie try \"chmod a+rw %s\" as root)", pcPort, pcPort);
			perror ("");
			return false;
		}

		// Get present configuration so that we can fully restore port when done
		oldSerialInfo.reserved_char[0] = 0;
		if  (ioctl (m_hComm, TIOCGSERIAL, &oldSerialInfo) < 0) {
			perror ("CSerialPort::Open () error getting configuration");
			close (m_hComm);
			m_hComm = -1;
			return false;
		}

		newSerialInfo = oldSerialInfo;
		tcgetattr (m_hComm, &oldSettings);
		newSettings = oldSettings;

		// raw 8-bit use:
		cfmakeraw (&newSettings);

		// non-canonical input settings:
		newSettings.c_cc[VMIN] = 1;       // min characters to read, blocks if > 0
		newSettings.c_cc[VTIME] = 0;       // timeout in deciseconds

		// baud rate:
		switch  (m_iBaud) {
			case 1200L:
				cfsetospeed (&newSettings, B1200);
				cfsetispeed (&newSettings, B1200);
				break;
			case 2400L:
				cfsetospeed (&newSettings, B2400);
				cfsetispeed (&newSettings, B2400);
				break;
			case 4800L:
				cfsetospeed (&newSettings, B4800);
				cfsetispeed (&newSettings, B4800);
				break;
			case 9600L:
				cfsetospeed (&newSettings, B9600);
				cfsetispeed (&newSettings, B9600);
				break;
//			case 14400L:
//				cfsetospeed (&newSettings, B14400);
//				cfsetispeed (&newSettings, B14400);
//				break;
			case 19200L:
				cfsetospeed (&newSettings, B19200);
				cfsetispeed (&newSettings, B19200);
				break;
//			case 28800L:
//				cfsetospeed (&newSettings, B28800);
//				cfsetispeed (&newSettings, B28800);
//				break;
			case 38400L:
				cfsetospeed (&newSettings, B38400);
				cfsetispeed (&newSettings, B38400);
				break;
//			case 56000L:
//				cfsetospeed (&newSettings, B56000);
//				cfsetispeed (&newSettings, B56000);
//				break;
			case 576000L:
				cfsetospeed (&newSettings, B57600);
				cfsetispeed (&newSettings, B57600);
				break;
			default:
				printf ("CSerialPort::Open () requested baud rate %d unsupported\n", m_iBaud);
				close (m_hComm);
				m_hComm = -1;
				return false;
		}

		// Set hardware flow control on/off:
		switch  (m_enFlow) {
			case FLOW_CONTROL_NONE_E:  	// disable flow control
				newSettings.c_iflag &= ~CRTSCTS;
				newSettings.c_oflag &= ~CRTSCTS;
				break;
			case FLOW_CONTROL_CTS_E:  	// enable flow control
				newSettings.c_iflag |= CRTSCTS;
				newSettings.c_oflag |= CRTSCTS;
				break;
			default:
				printf ("CSerialPort::Open () requested illegal flow control value\n");
				close (m_hComm);
				m_hComm = -1;
				return false;
		}

		// Set port configuration
		if  (tcsetattr (m_hComm, TCSANOW, &newSettings)) {
			perror ("CSerialPort::Open () error setting attributes");
			close (m_hComm);
			m_hComm = -1;
			return false;
		}

		return true;
	} catch  (...) {
		return false;
	}
}

// Close
//
// Description:	Closes the opened port. Can be called directly, but is also
//				called automatically when the CSerialPort object is destructed.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	void
//
bool CSerialPort::Close (void) {
	try {
		// return if already closed - return true or false?
		if  (m_hComm < 0)
			return false;

		// restore port settings
		if  (ioctl (m_hComm, TIOCSSERIAL, &oldSerialInfo) < 0) {
			perror ("CSerialPort::Close () error - cannot restore configuration");
			close (m_hComm);
			m_hComm = -1;
			return false;
		}

		// restore attributes
		if  (tcsetattr (m_hComm, TCSANOW, &oldSettings) < 0) {
			perror ("CSerialPort::Close () error - cannot restore attributes");
			close (m_hComm);
			m_hComm = -1;
			return false;
		}

		close (m_hComm);
		m_hComm = -1;

		return true;

	} catch  (...) {
		return false;
	}
}

// Reset
//
// Description: Reset port. Rx and Tx buffers are flushed.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	void
//
bool CSerialPort::Reset (void) {
	try {
		// return if not open
		if  (m_hComm < 0)
			return false;

		// flush both input and output buffers
		if  (!tcflush (m_hComm, TCIOFLUSH))
			return false;

		return true;
	} catch  (...) {
		return false;
	}
}


// Read
//
// Description:
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	unsigned char * ucBuffer
//				unsigned int uiNumOfBytes
//				unsigned int &uiNumOfBytesRead
//
bool CSerialPort::Read (void * pvBuffer, unsigned int uiNumOfBytes, unsigned long * pulNumOfBytesRead) {
	try {
		// return if not open
		if  (m_hComm < 0) {
			printf ("CSerialPort::Read () error: port closed\n");
			return false;
		}

		int rc = read (m_hComm, pvBuffer,  (size_t)uiNumOfBytes);
		if  (rc < 0) {
			* pulNumOfBytesRead = 0;
			return false;
		}
		* pulNumOfBytesRead = rc;

		return true;
	} catch  (...) {
		return false;
	}
}



bool CSerialPort::ReadBlocking (void * pvBuffer, unsigned int uiNumOfBytes, unsigned long * pulNumOfBytesRead) {
	try {
		return Read (pvBuffer, uiNumOfBytes, pulNumOfBytesRead);
	} catch  (...) {
		return false;
	}
}



// Write
//
// Description:	Write one or more bytes to the serial port.
//
// Return:		bool	-	true if successful, otherwise false.
//
// Parameter:	unsigned char * pucBuffer			-	Contains the data to
//														be written.
//				unsigned int uiNumOfBytes			-	Number of bytes to
//														write.
//
bool CSerialPort::Write (void * pvBuffer, unsigned int uiNumOfBytes) {
	try {
		// return if not open
		if  (m_hComm < 0) {
			printf ("CSerialPort::Write () error: port closed\n");
			return false;
		}

		if  ( (unsigned int)write (m_hComm, pvBuffer, uiNumOfBytes) != uiNumOfBytes) {
			perror ("CSerialPort::Write () error: could not write data to locobuffer");
			return false;
		}

		return true;

	} catch  (...) {
		return false;
	}
}
#endif
